<template>
  <view
    v-if="isShow"
    :animation="animationData"
    :class="[transitionClass]"
    :style="transitionStyle"
    @tap.stop="handleClick"
  >
    <slot></slot>
  </view>
</template>

<script lang="ts" setup>
import { AnimationOptions, transitionProps, AnimationMode, transitionEmits } from '../../../types/tn-transition'
import { computed, nextTick, ref, watch, CSSProperties } from 'vue'
import useAnimation from '../lib/useAnimation'
import { getAnimationType, getAnimationMode } from '../lib/animationMap'

const props = defineProps(transitionProps)
const emits = defineEmits(transitionEmits)

// 标志是否显示
let isShow = ref<boolean>(false)
// 透明度
let opacityValue: number = 1
// 动画
let transformValue: string = ''
// 动画默认配置
let animationConfig: AnimationOptions = {
  duration: !!props?.duration 
    ? typeof props.duration === 'string' ? Number(props.duration) : props.duration
    : 300,
  timingFunction: 'ease',
  transformOrigin: '50% 50%',
  delay: 0
}

// 动画样式
const transitionStyle = computed<CSSProperties>(() => {
  let style: CSSProperties = {}
  if (transformValue != '') {
    style.transform = transformValue
  }
  if (animationConfig?.duration) {
    style.transitionDuration = animationConfig.duration / 1000 + 's'
  } else {
    style.transitionDuration = 0.3 + 's'
  }
  
  style.opacity = opacityValue
  return Object.assign(style, props?.customStyle || {})
})

// 动画对应的类
const transitionClass = computed<string>(() => {
  let clazz: string[] = []

  if (!!props?.customClass) {
    clazz.push(props.customClass)
  }

  return clazz.join(' ')
})

// 动画操作相关函数
const { animation, animationTimer, animationData, createAnimation, step, run, clearAnimation } = useAnimation()

// 重新初始化动画
const initAnimation = (option?: AnimationOptions) => {
  if (!isShow.value) isShow.value = true
  nextTick(() => {
    animationConfig = Object.assign(animationConfig, option || {})
    createAnimation(animationConfig)
  })
}

// 对动画进行分组
const stepAnimation = (config?: AnimationOptions) => {
  step(config)
}

// 开始触发动画
const runAnimation = (fn?: Function) => {
  run(fn)
}

// 处理动画开始前的默认样式
const styleInit = (type: boolean) => {
  let style: {
    transform: string,
    opacity: number
  } = {
    transform: '',
    opacity: 0
  }
  // 构建样式
  const buildStyle = (type: boolean, mode: AnimationMode) => {
    if (mode === 'fade') {
      style.opacity = Number(getAnimationType(type)[mode])
    } else {
      style.transform += getAnimationType(type)[mode] + ''
    }
  }

  if (!!props?.mode) {
    if (typeof props.mode === 'string') {
      buildStyle(type, props.mode as AnimationMode)
    } else {
      props.mode.forEach(mode => {
        buildStyle(type, mode as AnimationMode)
      })
    }
  }
  
  return style
}

// 处理内置组合动画
const transformInit = (type: boolean) => {
  // 内置动画函数类型
  type InnerAnimationMethodType = 'opacity' | 'translateX' | 'translateY' | 'scale'
  // 构建动画样式
  const buildTransform = (type: boolean, mode: AnimationMode) => {
    let animationValue: any = null
    if (mode === 'fade') {
      animationValue = type ? 0 : 1
    } else {
      animationValue = type ? '-100%' : '0'
      switch (mode) {
        case 'zoom-in':
          animationValue = type ? 0.8 : 1
          break
        case 'zoom-out':
          animationValue = type ? 1.2 : 1
          break
        case 'slide-right':
        case 'slide-bottom':
          animationValue = type ? '100%' : '0'
          break
      }
    }
    const getAnimationMethod = <T, K extends keyof T>(obj: T, methodName: K): T[K] => {
      return obj[methodName]
    }
    if (!animation.value) return
    // console.log(animation.value, animationValue, mode);
    // animation.value[getAnimationMode()[mode] as InnerAnimationMethodType](animationValue)
    const method = getAnimationMethod(animation.value, getAnimationMode()[mode] as InnerAnimationMethodType)
    method.apply(animation.value, [animationValue])
    // console.log(getAnimationMethod(animation.value, getAnimationMode()[mode] as InnerAnimationMethodType));
    // getAnimationMethod(animation.value, getAnimationMode()[mode] as InnerAnimationMethodType)(animationValue)
  }

  if (!!props?.mode) {
    if (typeof props.mode === 'string') {
      buildTransform(type, props.mode as AnimationMode)
    } else {
      props.mode.forEach(mode => {
        buildTransform(type, mode as AnimationMode)
      })
    }
  }
}

// 开始执行动画
const open = () => {
  if (animationTimer.value) {
    clearTimeout(animationTimer.value)
  }
  transformValue = ''
  isShow.value = true
  let { opacity, transform } = styleInit(false)
  opacityValue = opacity
  transformValue = transform
  // 确保动态样式已经生效后，执行动画，如果不加 nextTick ，会导致 wx 动画执行异常
  nextTick(() => {
    // TODO 定时器保证动画完全执行，目前有些问题，后面会取消定时器
    setTimeout(() => {
      createAnimation(animationConfig)
      transformInit(false)
      step()
      run()
      emits('enter', isShow.value)
    }, 20);
  })
}
// 关闭时执行动画
const close = () => {
  if(!animation.value) return
  transformInit(true)
  step()
  run(() => {
    isShow.value = false
    let { opacity, transform } = styleInit(false)
    opacityValue = opacity
    transformValue = transform
    clearAnimation()
    emits('leave', isShow.value)
  })
}
// 监听show状态, 判断是否显示
watch(() => props.show, (nVal: boolean) => {
  if (nVal) {
    open()
  } else {
    // 避免上来就执行 close,导致动画错乱
    if (isShow.value) {
      close()
    }
  }
}, {
  immediate: true
})

// 处理点击事件
const handleClick = () => {
  emits('click')
}

defineExpose({
  animation,
  initAnimation,
  stepAnimation,
  runAnimation
})
</script>

<script lang="ts">
export default {
  options: {
    // 在微信小程序中将组件节点渲染为虚拟节点，更加接近Vue组件的表现(不会出现shadow节点下再去创建元素)
    virtualHost: true
  }
}
</script>

<style lang="scss" scoped>
</style>
